package com.zhanglinwei.openApiServer.collect;

import com.zhanglinwei.common.SpringContext;
import com.zhanglinwei.common.utils.AssertUT;
import com.zhanglinwei.common.utils.RegUtils;
import com.zhanglinwei.openApiServer.annotation.OpenApi;
import com.zhanglinwei.openApiServer.annotation.OpenApiReference;
import com.zhanglinwei.openApiServer.context.OpenApiContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.support.AopUtils;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
@Component
public class AnnotationOpenApiCollect implements OpenApiCollect {

    @Override
    public void collect() {
        try {
            AtomicInteger counter = new AtomicInteger(0);
            Map<String, Object> openApiBeanMap = SpringContext.getBeansWithAnnotation(OpenApiReference.class);

            for (Object openApiBean : openApiBeanMap.values()) {
                Class<?> originClass = AopUtils.getTargetClass(openApiBean);

                if (needCollect(originClass)) {
                    for (Method method : originClass.getDeclaredMethods()) {
                        registryOpenApi(originClass, method, counter);
                    }
                }
            }

            log.info("Annotation collection completed. count: {}", counter.get());
        } catch (Exception ex) {
            log.info("AnnotationOpenApiCollect collection failed. msg: {}", ex.getMessage(), ex);
        }
    }



    private void registryOpenApi(Class<?> originClass, Method method, AtomicInteger counter) {
        OpenApi methodAnnotation = method.getAnnotation(OpenApi.class);
        if (isSkip(method, methodAnnotation)) {
            return;
        }
        validOpenApi(originClass, method);

        OpenApiContext.OpenApiMetadata openApiMetadata = OpenApiContext.OpenApiMetadata.builder()
                .apiCode(methodAnnotation.value())
                .apiVersion(methodAnnotation.version())
                .apiDescription(methodAnnotation.desc())
                .fullMethodName(method.getName())
                .fullClassName(originClass.getName())
                .build();
        OpenApiContext.registry(openApiMetadata);
        counter.incrementAndGet();
    }

    private void validOpenApi(Class<?> originClass, Method method) {
        OpenApi methodAnnotation = method.getAnnotation(OpenApi.class);
        int methodParameterCount = method.getParameterCount();
        String originClassName = originClass.getName();
        String methodName = method.getName();
        String apiCode = methodAnnotation.value();
        String version = methodAnnotation.version();

        AssertUT.isTrue(RegUtils.ofPattern("^[a-zA-Z]+(?:\\.[a-zA-Z]+){2}$", apiCode), String.format("The %s doesn't conform to the rules 【xx.xx.xx】 ", apiCode));
        AssertUT.isTrue(RegUtils.ofPattern("^[1-9]\\d*\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$", version), String.format("The %s doesn't conform to the rules 【1.0.0】 ", version));
        AssertUT.isTrue(
                methodParameterCount <= 1,
                String.format(
                        "OpenApi methods must have 0 or 1 parameter - invalid method: [%s#%s] found %d parameters",
                        originClassName, methodName, methodParameterCount
                )
        );
        if (methodParameterCount > 0) {
            Class<?> parameterType = method.getParameterTypes()[0];
            AssertUT.isTrue(
                    !Collection.class.isAssignableFrom(parameterType) && !parameterType.isArray(),
                    String.format(
                            "OpenApi method parameter must not be collection or array - invalid method: [%s#%s] type %s",
                            originClassName, methodName, parameterType.getSimpleName()
                    )
            );
        }
    }

    private boolean needCollect(Class<?> originClass) {
        OpenApiReference openApiReference = originClass.getAnnotation(OpenApiReference.class);
        return openApiReference != null && openApiReference.enable();
    }

    private boolean isSkip(Method method, OpenApi methodAnnotation) {
        if (methodAnnotation == null || !methodAnnotation.enable()) {
            return true;
        }
        if (method.isSynthetic()) {
            return true;
        }
        if (Modifier.isStatic(method.getModifiers())) {
            return true;
        }

        return !Modifier.isPublic(method.getModifiers());
    }

}
